# -*- coding: utf-8 -*-

import pprint

from hlm.session import Session
from hlm.statistic import *
from hlm.ui import ui_cprint, UIColor, ui_print_form

from uut.design_unit_test import DesignUnitTest


def generate_max_usage_report(uut_max_usage: dict):
    # Generate report data.
    report_data = {}

    def update_report(item_key, item_value):
        if item_key in report_data:
            report_data[item_key] = max(item_value, report_data[item_key])
        else:
            report_data[item_key] = item_value
    # We need to focus on the following ports:
    #  Peer Level
    #  - E-Runner -> Softmem
    #  - Shadow Buffer -> Softmem
    #  - M-Runner <-> Softmem
    #  - Router -> Softmem
    #  - Softmem <-> ALUs
    #  - Softmem -> Arbiter
    #  Peer-to-Peer Level
    #  - M-Runner <-> M-Runner
    peers = []
    peer_cluster = uut_max_usage['n']['cluster']
    peer_groups = peer_cluster['n']['peer_groups']
    for node_pg in peer_groups:
        peers += node_pg['n']['peers']
        # M-Runner <-> M-Runner
        update_report('mr_to_mr',
                      node_pg['n']['mrunner']['p']['chain_in']['Bus']['data'])
        update_report('mr_to_mr',
                      node_pg['n']['mrunner']['p']['message_in']['Bus']['data'])
        # Softmem -> Arbiter
        update_report('sm_to_a',
                      node_pg['n']['arbiter_store'][0]['p']['receiver_lanes'][0]['data'])
    for node_p in peers:
        # Statistic the information we need.
        softmem_ports = node_p['n']['softmem']['p']
        mrunner_ports = node_p['n']['mrunner']['p']
        # E-Runner -> Softmem
        update_report('er_to_sm',
                      softmem_ports['erunner_receivers'][0]['Read']['dst_uri'])
        # Shadow Buffer -> Softmem
        update_report('sb_to_sm',
                      softmem_ports['shadow_buffer']['Bus']['dst_uri'])
        # M-Runner <-> Softmem
        update_report('mr_to_sm',
                      softmem_ports['mrunner_receiver']['Bus']['data'])
        update_report('sm_to_mr',
                      mrunner_ports['message_in']['Bus']['data'])
        # Router -> Softmem
        update_report('r_to_sm',
                      softmem_ports['memory_receivers'][0]['Bus']['data'])
        # Softmem <-> ALUs.
        for alu_port in softmem_ports['alu_senders']:
            update_report('alu_to_sm',
                          alu_port['Dst']['addr'])
        for alu_port in node_p['n']['alus']:
            update_report('sm_to_alu',
                          alu_port['p']['receiver']['Src']['addr'])
        # M-Runner -> M-Runner
        update_report('mr_to_mr',
                      mrunner_ports['chain_in']['Bus']['data'])
    # Convert the report data into a table.
    report_form = [['Type', 'Max Usage'],
                   ['E-Runner -> Softmem', report_data['er_to_sm']],
                   ['Shadow Buffer -> Softmem', report_data['sb_to_sm']],
                   ['M-Runner -> Softmem', report_data['mr_to_sm']],
                   ['Softmem -> M-Runner', report_data['sm_to_mr']],
                   ['Router -> Softmem', report_data['r_to_sm']],
                   ['Softmem -> ALUs', report_data['sm_to_alu']],
                   ['ALUs -> Softmem', report_data['alu_to_sm']],
                   ['Softmem -> Arbiter', report_data['sm_to_a']],
                   ['M-Runner <-> M-Runner', report_data['mr_to_mr']]]

    return report_form


def main():
    print('----- Start Session -----')
    session = Session(DesignUnitTest)
    uut = session.top_module()

    max_simulate_steps = 100000
    # max_simulate_steps = 350

    import time
    start = time.clock()
    halt_int = False
    for step in range(max_simulate_steps):
        if step % 100 == 0:
            ui_cprint(UIColor.GREEN,
                      '> STEP: [{:3d}]'.format(session.session_step()))
        session.next_step()
        # Detect the halt.
        if uut.port.halt_int.get():
            if step % 100 != 0:
                ui_cprint(UIColor.GREEN,
                          '> STEP: [{:3d}]'.format(session.session_step()))
            halt_int = True
            print('Chip is halt.')
            break
    if not halt_int:
        print('After {} steps still does not halt, stop it.'.format(max_simulate_steps))
    elapsed = (time.clock() - start)
    print("Time used:", elapsed)

    # Flush the main memory.
    uut.node.main_memory.flush()

    def print_softmem(data, start=0, end=-1, row=8):
        if end == -1:
            end = len(data)
        counter = 0
        for ii in range(start, end):
            if counter == row:
                print()
                counter = 0
            print(('%4d' % ii),
                  's' if data[ii].sb else ' ',
                  'i' if data[ii].msgin else ' ',
                  'a' if data[ii].ain else ' ',
                  'data:', str(data[ii].data).rjust(10), end='')
            counter += 1
        print()

    def print_mainmem(data, start=0, end=-1, row=8):
        if end == -1:
            end = len(data)
        counter = 0
        for ii in range(start, end):
            if counter == row:
                print()
                counter = 0
            print(str(data[ii]).rjust(8), end='')
            counter += 1
        print()

    print('Main Memory')
    result_addr = 49264
    print_mainmem(uut.node.main_memory.reg.data[result_addr : result_addr + 64 * 64], row=64)

    print('Peer 0 Softmem')
    print_softmem(uut.node.chip.node.cluster.node.peer_groups[0].node.peers[0].node.softmem.reg.data)
    # print('Peer 1 Softmem')
    # print_softmem(uut.node.chip.node.cluster.node.peer_groups[0].node.peers[1].node.softmem.reg.data)
    # print('Peer 4 Softmem')
    # print_softmem(uut.node.chip.node.cluster.node.peer_groups[1].node.peers[0].node.softmem.reg.data)

    # Fetch the maximum usage.
    print()
    print('Port Usage Report')
    ui_print_form(
        generate_max_usage_report(statistic_port_max_usage(uut.node.chip)))


if __name__ == '__main__':
    main()